#include "Scanner.h"
#include "Parser.tab.h"

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Parser
 *      Method:  yylex
 * Description:  Chama o scanner para recuperar próximo token
 *--------------------------------------------------------------------------------------
 */
int yylex(Parser::semantic_type *yylval, Parser::location_type *yyloc,
        Driver &driver)
{
    return driver.scanner->yylex(yylval);
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Parser
 *      Method:  error
 * Description:  Função de erro para análise sintática
 *--------------------------------------------------------------------------------------
 */
void Parser::error(const Parser::location_type &loc, const string &msg)
{
    driver.saveCurrent();
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Parser
 *      Method:  printError
 * Description:  Imprime um erro
 *--------------------------------------------------------------------------------------
 */
void printError(ErrorWhat what, int synToken, Driver &driver)
{
    driver.sinError(what, synToken);
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Parser
 *      Method:  addProgram
 * Description:  Adiciona um programa
 *--------------------------------------------------------------------------------------
 */
void addProgram(const string* name, Driver &driver)
{
    driver.symbols.addProgram(*name);
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Parser
 *      Method:  addConstant
 * Description:  Adiciona uma constante
 *--------------------------------------------------------------------------------------
 */
void addConstant(const string* name, const VarValue &value, Driver &driver)
{
    if(value.error)
        return;

    // Cria tipo Variable
    Variable var;
    var.type = value.type;

    if(var.type == INTEGER)
        var.ivalue = value.ivalue;
    else
        var.fvalue = value.fvalue;

    // Adiciona na tabela de símbolos
    if(!driver.symbols.addConstant(*name, var))
        driver.semError(SEM_ALREADY_DECLARED, *name);

    driver.generator.addConstant(*name, value);
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Parser
 *      Method:  addVariable
 * Description:  Adiciona uma variável
 *--------------------------------------------------------------------------------------
 */
void addVariable(const VarList* list, const VarValue &value, Driver &driver)
{
    if(value.error)
        return;

    // Cria tipo variável
    Variable var;
    var.type = value.type;

    // Adiciona todas as variáveis
    for(VarArg arg : *list)
    {
        if(!driver.symbols.addVariable(arg.name, var))
            driver.semError(SEM_ALREADY_DECLARED, arg.name);

        driver.generator.addVariable(arg.name);
    }
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Parser
 *      Method:  addProcedure
 * Description:  Adiciona um procedimento
 *--------------------------------------------------------------------------------------
 */
void startProcedure(const string* name, const VarList* vlist, Driver &driver)
{
    if(name == NULL)
    {
        driver.symbols.pushLevel();
        return;
    }

    // Cria a estrutura Procedure
    Procedure proc;
    proc.params = new list<int>;
    for(VarArg arg : *vlist)
        proc.params->push_back(arg.type);

    // Tenta inserir na tabela de símbolos
    if(!driver.symbols.addProcedure(*name, proc))
    {
        driver.semError(SEM_ALREADY_DECLARED, *name);
        delete proc.params;
    }

    driver.generator.startProcedure(*name, *vlist);

    // Aumenta o nível
    driver.symbols.pushLevel();

    // Adiciona os parâmetros
    for(VarArg arg : *vlist)
    {
        Variable var;
        var.type = arg.type;

        if(!driver.symbols.addParameter(arg.name, var))
            driver.semError(SEM_ALREADY_DECLARED, arg.name);
    }
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Parser
 *      Method:  endProcedure
 * Description:  Encerra um procedimento
 *--------------------------------------------------------------------------------------
 */
void endProcedure(Driver &driver)
{
    driver.symbols.popLevel();
    driver.generator.endProcedure();
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Parser
 *      Method:  startIf
 * Description:  Começa um if
 *--------------------------------------------------------------------------------------
 */
void startIf(const ExpressionTree* exp, Driver &driver)
{
    checkExpression(exp, driver);
    driver.generator.startIf(*exp);
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Parser
 *      Method:  startElse
 * Description:  Começa um else
 *--------------------------------------------------------------------------------------
 */
void startElse(Driver &driver)
{
    driver.generator.startElse();
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Parser
 *      Method:  endIf
 * Description:  Termina um if
 *--------------------------------------------------------------------------------------
 */
void endIf(Driver &driver)
{
    driver.generator.endIf();
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Parser
 *      Method:  startWhile
 * Description:  Começa um while
 *--------------------------------------------------------------------------------------
 */
void startWhile(const ExpressionTree* exp, Driver &driver)
{
    checkExpression(exp, driver);
    driver.generator.startWhile(*exp);
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Parser
 *      Method:  endWhile
 * Description:  Termina um while
 *--------------------------------------------------------------------------------------
 */
void endWhile(Driver &driver)
{
    driver.generator.endWhile();
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Parser
 *      Method:  startRepeat
 * Description:  Começa um repeat
 *--------------------------------------------------------------------------------------
 */
void startRepeat(Driver &driver)
{
    driver.generator.startRepeat();
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Parser
 *      Method:  endRepeat
 * Description:  Termina um repeat
 *--------------------------------------------------------------------------------------
 */
void endRepeat(const ExpressionTree* exp, Driver &driver)
{
    checkExpression(exp, driver);
    driver.generator.endRepeat(*exp);
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Parser
 *      Method:  checkAttribution
 * Description:  Verifica uma atribuição
 *--------------------------------------------------------------------------------------
 */
void checkAttribution(const string* name, const ExpressionTree* exp, Driver &driver)
{
    Variable* var = driver.symbols.getVariable(*name);

    // Verifica se a variável foi declarada
    if(var == NULL)
    {
        driver.semError(SEM_VAR_NOT_DECLARED, *name);
        return;
    }

    // Verifica a caregoria
    Category cat = driver.symbols.getCategory(*name);
    if(cat == CONSTANT)
    {
        driver.semError(SEM_CONST_ATTRIB, *name);
        return;
    }

    int et = checkExpression(exp, driver);

    // Verifica os tipos para a atribuição
    if(var->type == INTEGER &&  et == FLOAT)
        driver.semError(SEM_ATTRIBUTION, *name);

    driver.generator.addAttribution(*name, *exp);
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Parser
 *      Method:  checkProcedureCall
 * Description:  Verifica uma chamada a um procedimento
 *--------------------------------------------------------------------------------------
 */
void checkProcedureCall(const string* name, const VarList* vlist, Driver &driver)
{
    Procedure* proc = driver.symbols.getProcedure(*name);

    // Verifica se o procedimento foi declarado
    if(proc == NULL)
    {
        driver.semError(SEM_PROC_NOT_DECLARED, *name);
        return;
    }

    // Verifica a quantidade de parâmetros
    if(vlist->size() != proc->params->size())
    {
        driver.semError(SEM_PARAMETERS_ERROR, *name);
        return;
    }

    auto it1 = vlist->begin();
    auto it2 = proc->params->begin();

    // Verifica todos os tipos dos parâmetros
    for( ; it1 != vlist->end(); it1++, it2++)
    {
        Variable* var = driver.symbols.getVariable(it1->name);

        // Verifica se a variável foi declarada
        if(var == NULL)
        {
            driver.semError(SEM_VAR_NOT_DECLARED, it1->name);
            continue;
        }

        // Verifica o tipo
        if(var->type != *it2)
        {
            driver.semError(SEM_PARAMETERS_ERROR, *name);
            return;
        }
    }

    driver.generator.addProcedure(*name, *vlist);
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Parser
 *      Method:  checkLineCall
 * Description:  Verifica uma chamada a readln ou writeln
 *--------------------------------------------------------------------------------------
 */
void checkLineCall(int which, const VarList* list, Driver &driver)
{
    int type;
    bool first = true;
    string name = (which == WRITELN) ? "writeln" : "readln";

    // Verifica todos os tipos dos parâmetros
    for(auto it1 = list->begin(); it1 != list->end(); it1++)
    {
        Variable* var = driver.symbols.getVariable(it1->name);

        // Verifica se a variável foi declarada
        if(var == NULL)
        {
            driver.semError(SEM_VAR_NOT_DECLARED, it1->name);
            continue;
        }

        // Verifica se não for passado uma constante para o readln
        if(which == READLN && driver.symbols.getCategory(it1->name) == CONSTANT)
        {
            driver.semError(SEM_CONST_ATTRIB, it1->name);
            continue;
        }

        if(first)
            type = var->type, first = false;

        // Verifica o tipo
        if(type != var->type)
        {
            driver.semError(SEM_READLN_WRITELN, name);
            return;
        }

        if(which == READLN)
            driver.generator.addReadln(it1->name);
        else
            driver.generator.addWriteln(it1->name);
    }
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  Parser
 *      Method:  checkExpression
 * Description:  Verifica uma expressão e retorna seu tipo
 *--------------------------------------------------------------------------------------
 */
int checkExpression(const ExpressionTree* exp, Driver &driver)
{
    int type;

    // Caso for uma variável
    if(exp->type == VALUE)
    {
        Variable* var = driver.symbols.getVariable(*(exp->sval));

        // Verifica se a variável foi declarada
        if(var == NULL)
        {
            driver.semError(SEM_VAR_NOT_DECLARED, *(exp->sval));
            type = ERROR;
        }
        else
            type = var->type;
    }

    // Caso for operador
    else if(exp->type == OPERATOR)
    {
        // Tipo da esquerda
        int t1 = checkExpression(exp->left, driver);

        if(exp->right != NULL)
        {
            // Tipo da direita
            int t2 = checkExpression(exp->right, driver);

            if(t1 == ERROR || t2 == ERROR)
                type = ERROR;
            else if(t1 == FLOAT || t2 == FLOAT)
                type = FLOAT;
            else
                type = INTEGER;

            // Verifica os tipos de uma divisão
            if(exp->op == DIVISION && (t1 == FLOAT || t2 == FLOAT))
                driver.semError(SEM_DIVISION, "");
        }
        else
            type = t1;
    }

    // Caso for inteiro, float ou error, retorna o próprio tipo
    else
        type = exp->type;

    return type;
}

